﻿
namespace TestFsWeb

open System
open System.Net
open System.Text
open Microsoft.FSharp.Control.WebExtensions

open TestFsWeb.WebClientExtend
open TestFsWeb.Helper

module TestWebClient =

    // the data downloaded from the server only contains body
    // the headers are not included in this part
    let private test_sync_downloaddata(address : string) =
        use client = new WebClient()
        
        address
        |> client.DownloadData
        |> Encoding.UTF8.GetString
        |> printfn "%s"

    // it can deal with the address without or without slash
    let private test_sync_downloadstring(address : string) =
        use client = new WebClient()

        address 
        |> client.DownloadString
        |> printfn "%s"

    let private test_async_downloadstring() =

        let download (addresses : seq<string>) =
            async {
                use client = new WebClient()

                for address in addresses do
                    client.DownloadStringAsync <| new Uri(address)
                    let! args = Async.AwaitEvent client.DownloadStringCompleted

                    if args.Cancelled then
                        printfn "downloading[%s] is cancelled" address
                    elif args.Error <> null then
                        printfn "failed to download[%s]: %s" address args.Error.Message
                    else
                        printfn "\t%d characters downloaded from [%s]" args.Result.Length address
            } |> Async.StartImmediate

            pause_for("exit")  
        
        ["http://msdn.microsoft.com/en-us/default.aspx";
        "http://worldnews.msnbc.msn.com/_news/2012/02/28/10529610-hillary-clinton-syrias-assad-could-be-labeled-a-war-criminal"]
        |> download    
        
    let private test_async_downloadstring_extension() =
        async {
            use client = new WebClient()

            // ------------------ successful download
            let! content = client.AsyncDownloadString <| new Uri("http://msdn.microsoft.com/en-us/default.aspx")
            printfn "%d characters are successfully downloaded" content.Length

            // ------------------ failure
            try
                let! _ = client.AsyncDownloadString <| new Uri("http://xxx:50000/")
                ()
            with
                | ex -> printfn "%s" ex.Message            
        }
        |> Async.StartImmediate
        pause_for("test cancel")

        // ------------------ cancelled
        use client = new WebClient()
        async {
            do! Async.Sleep 500
            client.CancelAsync()
            printfn "webclient is cancelled"
        } |> Async.StartImmediate

        try
            let nonExist = 
                client.AsyncDownloadString(new Uri("http://xxx:50000/")) 
                |> Async.RunSynchronously
            failwith "impossible to execute this line"
        with
            | :? System.OperationCanceledException ->
                printfn "downloading is cancelled."

    let private test_downloadfile() =
        use client = new WebClient()

        // **************** SYNC mode
        client.DownloadFile("http://www.microsoft.com/en-us/default.aspx","sync-saved.html")
        printfn "sync-downloading succeeded."

        // **************** ASYNC mode
        // !!!!!!!!!! DownloadProgressChanged can be fired concurrently
        // !!!!!!!!!! so the handler of this event can have the "race risk"
        client.DownloadProgressChanged.Add (fun evtargs-> printfn "downloaded %d of %d bytes, %d percentage completed" evtargs.BytesReceived evtargs.TotalBytesToReceive evtargs.ProgressPercentage)

        client.DownloadFileCompleted.Add (fun evtargs ->
                                                if evtargs.Cancelled then
                                                    printfn "Cancelled"
                                                elif evtargs.Error <> null then
                                                    printfn "!!! Error: %s" evtargs.Error.Message 
                                                else
                                                    printfn "######### async-downloading succeeded.#########")
        client.DownloadFileAsync(new Uri("http://cn.msn.com/"),"async-saved.html")
        pause_for("exit")

    let private test_both() =
        let address = "http://localhost:50000/"
        
        // ---------------- run in thread pool to prevent blocking
        async {
            use server = new SimpleHttpServer(address)
            server.Run()
        } |> Async.Start

        // ---------------- client side
        pause_for("start client")
        use client = new WebClient()

        // set headers
        [(HttpRequestHeader.Accept,"application/x-www-form-urlencoded");
        (HttpRequestHeader.AcceptLanguage,"en-us");
        (HttpRequestHeader.UserAgent,"chrome");
        (HttpRequestHeader.KeepAlive,"keep-alive");
        (HttpRequestHeader.Host,"localhost");]
        |> Seq.iter (fun(k,v) -> client.Headers.Add(k,v))

        address
        |> client.DownloadData
        |> Encoding.UTF8.GetString
        |> printfn "\nResponse from the server: \n%s"

    // send and check the headers received
    let private test_headers() =
        use client = new WebClient()
        client.Headers.Add(HttpRequestHeader.AcceptCharset,"gzip")

        let received = client.DownloadData("http://www.dotnetperls.com/")
        printfn "%d bytes received" received.Length

        [HttpResponseHeader.Connection;
        HttpResponseHeader.ContentEncoding;
        HttpResponseHeader.ContentLength;
        HttpResponseHeader.CacheControl]
        |> Seq.iter (fun name -> 
                                let header = client.ResponseHeaders.[name]
                                let toprint = 
                                    if header = null then "!!! Not contained"
                                    else header
                                printfn "[%-20O]: %s" name toprint)

    // !!!!!! chekanote: WebClient can be used for local file IO
    // inside the WebClient, it uses "AsyncOperationManager" to ensure the event handler
    // will be executed on the right thread or context
    // and it will create the FileStream in async mode
    let private test_readfile_eap(address: string) =
        use client = new WebClient()

        // ---------------------------- event based
        client.DownloadProgressChanged.Add (fun evtargs -> 
                                                // use Console.WriteLine here instead of printfn
                                                // is because printfn has multi-thread issue
                                                // text printed on the screen can be mixed when printed from 
                                                // multiple threads
                                                System.Console.WriteLine("{0}% readed",evtargs.ProgressPercentage))

        client.DownloadDataCompleted.Add (fun evtargs -> 
                                                if evtargs.Error <> null then
                                                    printfn "!!!!!! Error: %s" evtargs.Error.Message
                                                else 
                                                    printfn "%d bytes readed" evtargs.Result.Length)

        // the address here must start with "file" and must be absolute path
        client.DownloadDataAsync(new Uri(address))

        pause_for("exit")

    let private test_downloaddata_asyncbinding() = 
        async {
            use client = new WebClient()

            let! bytes1 = client.AsyncDownloadData @"file://D:\Study\programming\DotNet\LearnByTest\FSharp\TestFsWeb\bin\Debug\async-saved.html"
            printfn "[1] from file: %d bytes read" bytes1.Length

            let! bytes2 = client.AsyncDownloadData "http://www.microsoft.com/en-us/default.aspx"
            printfn "[2] from web: %d bytes read" bytes2.Length

            try
                let! _ = client.AsyncDownloadData "http://xxxxxxxxxxx.org"
                ()
            with
                | ex -> printfn "[3] Error: %s" ex.Message
        } |> Async.StartImmediate

        pause_for("exit")

    let private test_async_writefile() =
        let contents = ["The System.Net.WebClient class is incredibly handy for a variety of purposes. Given its name and placement in the System.Net namespace, it's not surprising that most folks think of it as being only for Web-related activities: downloading files from an HTTP server, uploading files to an FTP site, and the like. But WebClient is nicely abstracted on top of WebRequest and WebResponse, which support a pluggable factory model for creating concrete implementations of each of these types. Calling WebRequest.Create with an HTTP URL will return an instance of an HttpWebRequest, just as calling it with an FTP URL will return an instance of an FtpWeb­Request. WebClient uses WebRequest and WebResponse internally to implement a whole slew of useful functionality.";
        "WebClient internally uses AsyncOperationManager to ensure the callbacks happen in the right context, and FileWeb­Re­quest/FileWebResponse will use asynchronous I/O when the asynchronous methods on WebClient are being used"]
        let paths = [@"file://D:\Study\programming\DotNet\LearnByTest\FSharp\TestFsWeb\bin\Debug\uploaded1.txt";@"file://D:\Study\programming\DotNet\LearnByTest\FSharp\TestFsWeb\bin\Debug\uploaded2.txt"]

        async {
            use client = new WebClient()

            // if the path are the same, then later content will overwrite the previous ones
            for (path,content) in (Seq.zip paths contents) do
                let! response = client.AsyncUploadString path content
                printfn "upload response: %s" response // when upload to file, the response is empty
        } |> Async.StartImmediate

        pause_for "exit"

    let main() =
        // test_sync_downloaddata "http://www.w3schools.com/html/tryit.asp?filename=tryhtml_intro"
        // test_sync_downloadstring "http://www.dotnetperls.com" 
        // test_both()
        // test_headers()
        // test_async_downloadstring()
        // test_async_downloadstring_extension()
        // test_downloadfile()
        // test_readfile_eap(@"file://D:\Study\programming\DotNet\LearnByTest\FSharp\TestFsWeb\bin\Debug\async-saved.html")
        // test_downloaddata_asyncbinding()
        test_async_writefile()


